/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */

#ifndef _CUBEFS_CORE_API_REFLECTION_ARRAY_HPP
#define	_CUBEFS_CORE_API_REFLECTION_ARRAY_HPP

#include <cubefs/api/exceptions.hpp>
#include <cubefs/api/reflection/array.hpp>
#include <cubefs/core/api/reflection/list.hpp>

#define CUBEFS_SOURCE "cubefs/core/api/reflection/array.hpp:" CUBEFS_AT

namespace cubefs    {
namespace api       {

template <typename T, typename Interface>
class ArrayImplT: public TypedListImplT<Long, -1, Interface> {

	typedef Long BaseElementType;
	typedef ArrayImplT<T, Interface> Me;
    typedef TypedListImplT<Long, -1, Interface> Base;

public:
    ArrayImplT(): Base(), size_(0) {}
    ArrayImplT(Int size): Base(size / sizeof(BaseElementType) + (size % sizeof(BaseElementType) == 0 ? 0 : 1)), size_(size) {}

    virtual const T& GetValue(Int idx) const
    {
        if (idx >= 0 && idx < size_)
        {
            return get_byte(idx);
        }
        else {
            throw BoundsException(CUBEFS_SOURCE, "Index out of range", 0, size_, idx);
        }
    }

    virtual void SetValue(Int idx, const T& value) {
        if (idx >= 0 && idx < size_)
        {
            set_byte(idx, value);
        }
        else {
            throw BoundsException(CUBEFS_SOURCE, "Index out of range", 0, size_, idx);
        }
    }

    virtual void Insert(Int idx, const T& value)
    {
        if (idx <= size_) {
            make_room(idx, 1);
            set_byte(idx, value);
        }
        else {
            throw BoundsException(CUBEFS_SOURCE, "idx is outside bounds", 0, size_, idx);
        }
    }

    virtual void Insert(Int idx, const Array<T>* values, Int start, Int length)
    {
        if (values == NULL) throw NullPointerException(CUBEFS_SOURCE, "values argument must not be null");

        if (values->size() < start + length) {
            throw BoundsException(CUBEFS_SOURCE, "start + length is outsize values's size", start, length, values->size());
        }

        if (idx <= size_)
        {
            make_room(idx, length);

            for (Int c = 0; c < length; c++) {
                set_byte(idx + c, values->GetValue(start + c));
            }
        }
        else {
            throw BoundsException(CUBEFS_SOURCE, "idx is outside bounds", 0, size_, idx);
        }
    }

    virtual void Remove(Int start, Int length)
    {
        if (start > size_)
        {
            throw BoundsException(CUBEFS_SOURCE, "start is outsize bounds", 0, size_, start);
        }
        else if (start + length > size_)
        {
            throw BoundsException(CUBEFS_SOURCE, "start + length is outsize bounds", start, length, size_);
        }
        else {
            remove_room(start, length);
        }
    }

    virtual void Clear() {
        Base::Clear();
        size_ = 0;
    }

    virtual void Resize(Int size)
    {
        Int items = size / sizeof(BaseElementType) + (size % sizeof(BaseElementType) == 0 ? 0 : 1);

        Base::list().resize(items);
        size_ = size;
    }

    virtual Int size() const {
        return size_;
    }

    virtual const void* Addr(Int idx) const {
    	BaseElementType offs = idx % sizeof(BaseElementType);
    	const BaseElementType* addr = &Base::list()[idx / sizeof(BaseElementType)];
        return ((const char*)addr) + offs;
    }

    virtual void* Addr(Int idx)
	{
    	BaseElementType offs = idx % sizeof(BaseElementType);
    	BaseElementType* addr = &Base::list()[idx / sizeof(BaseElementType)];
        return ((char*)addr) + offs;
    }

    virtual void* ptr() {
    	return Addr(0);
    }

    virtual const void* ptr() const {
    	return Addr(0);
    }


    static void InitType() {
    	type_ = new AbstractType(TypeNameFactory<Interface>::name(), List::MyType());
    }

    static void InitOperations() {
    	AbstractType *type = static_cast<AbstractType*>(type_);

    	type->AddOperation(new BasicOperation<T (Me::*)(Int), const T& (Me::*)(Int) const>(type, "Get", "index", &Me::GetValue));
    	type->AddOperation(new BasicOperation<void (Me::*)(Int, T), void (Me::*)(Int, const T&)>(type, "Set", "index", "value", &Me::SetValue));

    	type->AddOperation(new BasicOperation<void (Me::*)(Int, T), void (Me::*)(Int, const T&)>(type, "Insert", "idx", "value", &Me::Insert));
    	type->AddOperation(new BasicOperation<void (Me::*)(Int, Array<T>*, Int, Int), void (Me::*)(Int, const Array<T>*, Int, Int)>(type, "Insert", "idx", "values", "start", "length", &Me::Insert));

    	type->AddOperation(new BasicOperation<void (Me::*)(Int), void (Me::*)(Int)>(type, "Resize", "size", &Me::Resize));
    	type->AddOperation(new BasicOperation<void (Me::*)(Int, Int), void (Me::*)(Int, Int)>(type, "Remove", "start", "length", &Me::Remove));
    }


protected:
    void make_room(Int idx, Int length)
    {
        const Int element_size = sizeof(BaseElementType);

        Int reminder = Base::list().size() * element_size - size_;

        Int actual_length = length - reminder;

        Int nitems = actual_length / element_size + (actual_length % element_size == 0 ? 0 : 1);

        for (Int c = 0; c < nitems; c++) Base::list().push_back(0);

        if (idx < size_) {
            for (Int c = size_ - 1; c >= idx; c--) {
                set_byte(length + c, get_byte(c));
            }
        }

        size_ += length;
    }

    void remove_room(Int idx, Int length)
    {
        Int nitems = length / sizeof(BaseElementType);

        if (idx < size_) {
            for (Int c = idx + length; c < size_; c++) {
                set_byte(c - length, get_byte(c));
            }
        }

        for (Int c = 0; c < nitems; c++) Base::list().pop_back();

        size_ -= length;
    }


    const T& get_byte(Int idx) const
    {
        const BaseElementType offs = idx % sizeof(BaseElementType);
        const BaseElementType* addr = &Base::operator [](idx / sizeof(BaseElementType));
        T*  baddr = ((T*)addr) + offs;
        return *baddr;
    }

    void set_byte(Int idx, const T& value)
    {
    	BaseElementType offs = idx % sizeof(BaseElementType);
    	BaseElementType* addr = &Base::operator [](idx / sizeof(BaseElementType));
        T* baddr = ((T*)addr) + offs;
        *baddr = value;
    }

    CUBEFS_DECLARE_REFLECTION;

private:
    Int size_;
};


template <typename T, typename Interface>
Type* ArrayImplT<T, Interface>::type_						= NULL;

template <typename T>
class ArrayImpl: public ArrayImplT<T, Array<T> > {
	typedef ArrayImplT<T, Array<T> > Base;
public:
	ArrayImpl(): Base() {}
	ArrayImpl(Int capacity): Base(capacity) {}
};

typedef ArrayImpl<Byte> ByteArrayImpl;

}
}

#undef CUBEFS_SOURCE

#endif



